/*
 * Copyright (C) 2005-2012 by U.S. Army Electronic Proving Ground, JBLM Office, JBLM, WA 98433.
 * Contract No.                        W91RUS-10-C-0001 
 * Contractor:                         ManTech International 
 * Project(s):                         STORM, C3 Driver, (RICS) 2, IMS, CCTT
 *  
 * This material may be reproduced by or for the US Government pursuant to the copyright license under the
 * clause of DFARS 252.227-7013 (Nov 1995). The Government's rights to use, modify, reproduce, release,
 * perform, display, or disclose this software are restricted by paragraph (b)(2) of the 252.227-7013, Rights
 * in Technical Data-Noncommercial Items (Nov 1995) and DFARS 252.227-7014 Rights in Noncommercial Computer
 * Software and Noncommercial Computer Software Documentation (Jun 1995).
 * 
 * Any reproduction of this software or portions thereof must reproduce this legend.
 *
 * @author Dan Forrest
 * @date 2012/04/17
 * 
 * File: ReflectionUtility.java
 *
 * Modifications:
 * RS-052412-- added getStaticFieldByReflection method to access static fields by reflection.
 * Needed Modifications:
 * RS-052412-- need to write junit method to test getStaticFieldByReflection method.
 */

package util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.NoSuchElementException;

/**
 * Contains methods used to indirectly access fields and methods of a given object or class.
 * 
 * @author Dan Forrest
 * @version 1.0
 * @date April 17, 2012
 */
public class ReflectionUtility {
    
    /**
     * Searches for the given field name in the given class. If the field name is not found, the
     * classes ancestors will be searched recursively for the field name. The search is always
     * performed from bottom to top (i.e. child classes are always searched before their parent
     * class). The field, matching the given field name, from the lowest class in the hierarchy will
     * be returned. A fields access modifier will not affect the search.
     * 
     * @param targetClass The class on which to search for the field.
     * @param fieldName The name of the field to retrieve.
     * 
     * @return The field matching the given name at the lowest level in the class hierarchy
     * 
     * @throws IllegalArgumentException If any of the parameters are null.
     * @throws NoSuchElementException if a matching field name cannot be found
     * 
     */
    private static Field findFieldRecursively(Class<?> targetClass, String fieldName) throws NoSuchElementException {
        
        if ((targetClass == null) || (fieldName == null)) {
            throw new IllegalArgumentException();
        } else {
            // do nothing
        }
        
        Field field = null;
        try {
            field = targetClass.getDeclaredField(fieldName);
        } catch (NoSuchFieldException e) {
            Class<?> superClass = targetClass.getSuperclass();
            if (superClass != null) {
                field = findFieldRecursively(superClass, fieldName);
            } else {
                throw new NoSuchElementException();
            }
        }
        return field;
    }
    
    /**
     * Get a field declared within the specified target object, or its ancestors, irrespective of
     * the field's visibility. The field is retrieved via reflection.
     * 
     * @param targetObject The object to get the field from.
     * @param fieldName The name of the field you want to get.
     * @return The object requested.
     * 
     * @throws IllegalAccessException If there is an error retrieving the field via reflection.
     * @throws IllegalArgumentException If any of the parameters are null.
     * 
     */
    public static Object getFieldByReflection(Object targetObject, String fieldName) throws IllegalAccessException {
        
        if ((targetObject == null) || (fieldName == null)) {
            throw new IllegalArgumentException();
        } else {
            // do nothing
        }
        
        Class<?> fieldClass;
        if (targetObject instanceof Class<?>) {
            fieldClass = (Class<?>)targetObject;
        } else {
            fieldClass = targetObject.getClass();
        }
        
        Field field = findFieldRecursively(fieldClass, fieldName);
        field.setAccessible(true);
        return field.get(targetObject);
    }
    
    /**
     * Get a field from within an objects parent class (or the parent class's ancestors)
     * irrespective of the fields visibility. The field is retrieved via reflection.
     * 
     * @param targetObject The object to get the field from.
     * @param fieldName The name of the field you want to get.
     * @return The object requested.
     * 
     * @throws IllegalAccessException If there is an error retrieving the field via reflection.
     * @throws IllegalArgumentException If any of the parameters are null.
     * 
     */
    public static Object getFieldByReflectionFromParent(Object targetObject, String fieldName) throws IllegalAccessException {
        
        if ((targetObject == null) || (fieldName == null)) {
            throw new IllegalArgumentException();
        } else {
            // do nothing
        }
        
        Class<?> fieldClass;
        if (targetObject instanceof Class<?>) {
            fieldClass = (Class<?>)targetObject;
        } else {
            fieldClass = targetObject.getClass();
        }
        
        Field field = findFieldRecursively(fieldClass.getSuperclass(), fieldName);
        field.setAccessible(true);
        return field.get(targetObject);
    }
    
    /**
     * Get a method with no parameters from a class using reflection.
     * 
     * @param targetObject The object or class that holds the given method.
     * @param methodName The name of the method.
     * 
     * @return The method with the given name and no parameters.
     * 
     * @throws NoSuchMethodException If there is an error retrieving the method via reflection.
     * 
     */
    public static Method getMethodByReflection(Object targetObject, String methodName) throws NoSuchMethodException {
        
        // return getMethodByReflection(targetObject, methodName, null);
        if ((targetObject == null) || (methodName == null)) {
            throw new IllegalArgumentException();
        } else {
            // do nothing
        }
        
        Class<?> methodClass;
        if (targetObject instanceof Class<?>) {
            methodClass = (Class<?>)targetObject;
        } else {
            methodClass = targetObject.getClass();
        }
        
        Method method = methodClass.getDeclaredMethod(methodName);
        method.setAccessible(true);
        
        return method;
        
    }
    
    /**
     * Get a method from a class using reflection.
     * 
     * @param targetObject The object or class that holds the given method.
     * @param methodName The name of the method.
     * @param parameters The classes of the method's parameters.
     * 
     * @return The method with the given name and with the given parameters.
     * 
     * @throws IllegalArgumentException If any of the parameters are null.
     * @throws NoSuchMethodException If there is an error retrieving the method via reflection.
     * 
     */
    public static Method getMethodByReflection(Object targetObject,
                                               String methodName,
                                               Class<?>[] parameters) throws NoSuchMethodException {
        
        if ((targetObject == null) || (methodName == null) || (parameters == null)) {
            throw new IllegalArgumentException();
        } else {
            // do nothing
        }
        
        Class<?> methodClass;
        if (targetObject instanceof Class<?>) {
            methodClass = (Class<?>)targetObject;
        } else {
            methodClass = targetObject.getClass();
        }
        
        Method method = methodClass.getDeclaredMethod(methodName, parameters);
        method.setAccessible(true);
        
        return method;
    }
    
    /**
     * To be used when the the method testing does not need the class to be instantiated - no
     * constructor only static methods.
     * 
     * 
     * @param clazz The static class
     * @param methodName static method name
     * @param args The arguments for the method.
     * @return Static Method.
     * @throws Exception
     */
    public static Object invokeStaticMethod(Class<?> clazz, String methodName, Object[] args) throws Exception {
        Class<?> c = clazz;
        try {
            Method[] ms = c.getDeclaredMethods();
            for (Method m : ms) {
                if (m.getName().equalsIgnoreCase(methodName)) {
                    m.setAccessible(true);
                    
                    return m.invoke(null, args);
                    
                } else {
                    // empty
                }
            }
            
            throw new Exception();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * Get a method with no parameters from an object's parent class using reflection.
     * 
     * @param targetObject The object or class that holds the given method.
     * @param methodName The name of the method.
     * 
     * @return The method with the given name and no parameters.
     * 
     * @throws NoSuchMethodException If there is an error retrieving the method via reflection.
     * 
     */
    public static Method getMethodByReflectionFromParent(Object targetObject, String methodName) throws NoSuchMethodException {
        
        return getMethodByReflectionFromParent(targetObject, methodName, null);
    }
    
    /**
     * This method is used to get a method from an object's parent class using reflection.
     * 
     * @param targetObject The object or class that holds the given method.
     * @param methodName The name of the method.
     * @param parameters The classes of the method's parameters.
     * 
     * @return The method with the given name and parameters.
     * 
     * @throws IllegalArgumentException If any of the parameters are null.
     * @throws NoSuchMethodException If there is an error retrieving the method via reflection.
     * 
     */
    public static Method getMethodByReflectionFromParent(Object targetObject,
                                                         String methodName,
                                                         Class<?>[] parameters) throws NoSuchMethodException {
        
        if ((targetObject == null) || (methodName == null) || (parameters == null)) {
            throw new IllegalArgumentException();
        } else {
            // do nothing
        }
        
        Class<?> methodClass;
        if (targetObject instanceof Class<?>) {
            methodClass = ((Class<?>)targetObject).getSuperclass();
        } else {
            methodClass = targetObject.getClass().getSuperclass();
        }
        
        Method method = methodClass.getDeclaredMethod(methodName, parameters);
        method.setAccessible(true);
        
        return method;
    }
    
    /**
     * Get a static field value
     * 
     * @param aClass the class where static field is declared.
     * @param fieldName name of field to retrieve.
     * @return the object requested.
     * @throws SecurityException If there is an error retrieving the field via reflection.
     * @throws NoSuchFieldException If no field with provided name exists within the class.
     * @throws IllegalArgumentException If there is an error retrieving the field via reflection.
     * @throws IllegalAccessException If there is an error retrieving the field via reflection.
     */
    // TODO write junit test
    public static Object getStaticFieldByReflection(Class<?> aClass, String fieldName) throws SecurityException,
                                                                                      NoSuchFieldException,
                                                                                      IllegalArgumentException,
                                                                                      IllegalAccessException {
        Field aField = aClass.getDeclaredField(fieldName);
        aField.setAccessible(true);
        return aField.get(null);
    }
    
    /**
     * Set a field defined in the given object or any of its ancestors, using reflection.
     * 
     * @param targetObject The object which contains (or whose ancestors contain) the field being
     *            set.
     * @param fieldName The name of the field you want to set.
     * @param value The value to be set.
     * 
     * @throws IllegalAccessException If there is an error setting the field via reflection.
     * @throws IllegalArgumentException If any of the parameters are null.
     * 
     */
    public static void setFieldByReflection(Object targetObject, String fieldName, Object value) throws IllegalAccessException {
        
        if ((targetObject == null) || (fieldName == null)) {
            throw new IllegalArgumentException();
        } else {
            // do nothing
        }
        
        Class<?> fieldClass;
        if (targetObject instanceof Class<?>) {
            fieldClass = (Class<?>)targetObject;
        } else {
            fieldClass = targetObject.getClass();
        }
        
        Field field = findFieldRecursively(fieldClass, fieldName);
        field.setAccessible(true);
        field.set(targetObject, value);
    }
    
}
